package service.facade.customer;

import application.MySession;
import dao.inte.*;
import domain.Comment;
import domain.MyConfiguration;
import domain.shop.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import service.ILoggerService;
import service.Searcher;
import service.mail.Mailer;
import service.velocity.VelocityService;

import javax.inject.Inject;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Transactional
public class ShopCustomerServiceImpl implements ShopCustomerService {

    @Autowired(required = true)
    private ILoggerService loggerService;
    @Inject
    private UserRepository userRepository;
    private ProductRepository productRepository;
    @Inject
    private ConfigurationRepository configurationRepository;
    @Inject
    private CommentRepository commentRepository;
    @Inject
    private CategoryRepository categoryRepository;
    @Inject
    private OrderRepository orderRepository;
    @Inject
    private PayMethodRepository payMethodRepository;
    @Inject
    private DeliveryMethodRepository deliveryMethodRepository;
    @Inject
    private BasketItemRepository basketItemRepository;
    @Inject
    private MySession mySession;
    @Inject
    private Mailer mailer;
    @Inject
    private VelocityService velocityService;
    @Autowired(required = true)
    Searcher searcher;

    public ShopCustomerServiceImpl() {
    }

    public ShopCustomerServiceImpl(ProductRepository productRepository,
                                   ConfigurationRepository configurationRepository, CommentRepository commentRepository,
                                   CategoryRepository categoryRepository, OrderRepository orderRepository,
                                   PayMethodRepository payMethodRepository, DeliveryMethodRepository deliveryMethodRepository,
                                   BasketItemRepository basketItemRepository, Mailer mailer, VelocityService velocityService) {

        this.productRepository = productRepository;
        this.configurationRepository = configurationRepository;
        this.commentRepository = commentRepository;
        this.categoryRepository = categoryRepository;
        this.orderRepository = orderRepository;
        this.payMethodRepository = payMethodRepository;
        this.deliveryMethodRepository = deliveryMethodRepository;
        this.basketItemRepository = basketItemRepository;
        this.mailer = mailer;
        this.velocityService = velocityService;
    }


    public void saveComment(Comment comment) throws Exception {
        commentRepository.save(comment);
    }


    public List<Comment> findCommentsByProductId(final Long Id) {
        return commentRepository.findByProductId(Id);
    }


    public void updateBasketItem(BasketItem basketItem) {
        mySession.updateBasketProduct(basketItem.getProduct(), basketItem.getMultiplicity());
        updateBasketTotalPrice(mySession);
    }


    public void addProductToBasket(Product product, Integer count) {
        if (count == null) {
            count = 1;
        }
        mySession.addProductToBasket(product, count);
        updateBasketTotalPrice(mySession);
    }


    public void addProductToBasket(Long productID, Integer count) {
        addProductToBasket(findProductById(productID), count);
    }


    public void removeProductFromBasket(Product product) {
        mySession.removeProductFromBasket(product);
        updateBasketTotalPrice(mySession);
    }

    private void updateBasketTotalPrice(MySession mySession) {
        BigDecimal totalPrice = new BigDecimal("0");
        totalPrice = totalPrice.setScale(2, RoundingMode.HALF_UP);
        BigDecimal tmp;
        Map<Long, Integer> basket = mySession.getBasket();
        for (Long productId : basket.keySet()) {
            Product product = productRepository.findById(productId);
            tmp = product.getPrice().multiply(BigDecimal.valueOf(basket.get(productId)));
            tmp = tmp.setScale(2, RoundingMode.HALF_UP);
            totalPrice = totalPrice.add(tmp);
            totalPrice = totalPrice.setScale(2, RoundingMode.HALF_UP);
        }
        mySession.setTotalPrice(totalPrice);
    }


    public void createNewOrder() throws Exception {
        Order newOrder = new Order();
        newOrder.setActive(true);
        newOrder.setPayMethod(payMethodRepository.findById(mySession.getPayMethodId()));
        newOrder.setDeliveryMethod(deliveryMethodRepository.findById(mySession.getDeliveryMethodId()));
        newOrder.setUser(userRepository.findById(mySession.getId()));

        newOrder.setDeliveryAddress(newOrder.getUser().getAddress());

        Map<Long, Integer> basket = mySession.getBasket();
        List<BasketItem> orderItems = new ArrayList<BasketItem>();
        BasketItem basketItem = null;
        Product p = null;
        for (Long productId : basket.keySet()) {
            basketItem = new BasketItem();
            basketItem.setMultiplicity(basket.get(productId));
            p = productRepository.findById(productId);
            p.increaseSold();
            productRepository.save(p);
            basketItem.setProduct(p);
            basketItem.setPrice(basketItem.getProduct().getPrice());
            orderItems.add(basketItem);
        }
        newOrder.setProducts(orderItems);
        BigDecimal totalPrice = mySession.getTotalPrice();
        if (mySession.getDeliveryMethod() == null) {
            throw new Exception("No Deliver Method Selected.");
        } else {
            totalPrice = totalPrice.add(deliveryMethodRepository.findById(
                    mySession.getDeliveryMethodId()).getPrice());
        }
        newOrder.setTotalPrice(totalPrice);
        orderRepository.save(newOrder);
        sendOrderMail(newOrder);
        mySession.reset();
        loggerService.log(ILoggerService.ActionType.ORDER_SENDED);
    }

    private void sendOrderMail(Order newOrder) throws Exception {
        String[] recipients = new String[2];
        final MyConfiguration config = configurationRepository.getConfig();
        recipients[0] = config.getAdministratorEmail();
        recipients[1] = newOrder.getUser().getCompanyEmail();
        mailer.postMail(recipients, config.getOrderMailSubject(), velocityService.getOrderMail(newOrder), config.getAdministratorEmail());
    }
    //TODO

    public void sendWizardOrderMail(String mail) throws Exception {
        String[] recipients = new String[1];
        final MyConfiguration config = configurationRepository.getConfig();
        recipients[0] = config.getAdministratorEmail();
        mailer.postMail(recipients, config.getOrderMailSubject(), mail, config.getAdministratorEmail());
    }

    public Product findProductById(Long productId) {
        return productRepository.findById(productId);
    }

//    private void updateOrderTotalPrice(Order order) {
//        BigDecimal bd;
//        if (order.getDeliveryMethod() == null) {
//            bd = new BigDecimal("0");
//        } else {
//            bd = new BigDecimal(order.getDeliveryMethod().getPrice());
//        }
//
//        bd = bd.setScale(2, RoundingMode.HALF_UP);
//        BigDecimal tmp;
    //        for (BasketItem bi : order.getProducts()) {
//            tmp = new BigDecimal(
//                    String.valueOf(bi.getPrice() * bi.getMultiplicity()));
//
//            tmp = tmp.setScale(2, RoundingMode.HALF_UP);
//            bd = bd.add(tmp);
//            bd = bd.setScale(2, RoundingMode.HALF_UP);
//        }
//        order.setTotalPrice(bd);
//    }

    public Category findCategoryById(final Long Id) {
        return categoryRepository.findById(Id);
    }


    public List<Product> findAllProducts() {
        return productRepository.findAll();
    }


    public PayMethod findPayMethodById(final Long Id) {
        return payMethodRepository.findById(Id);
    }


    public DeliveryMethod findDeliveryMethodById(final Long Id) {
        return deliveryMethodRepository.findById(Id);
    }


    public List<DeliveryMethod> findAllDeliveryMethods() {
        return deliveryMethodRepository.findAll();
    }


    public List<PayMethod> findAllPayMethods() {
        return payMethodRepository.findAll();
    }


    public List<Category> findCategoryRoots() {
        return categoryRepository.findRoots();
    }


    public List<Category> findCategoryChildsByParentId(final Long Id) {
        return categoryRepository.findChildsByParentId(Id);
    }


    public Set<Product> findRelatedProductsByProductId(final Long productId) {
        return productRepository.findRelatedProductsByProductId(productId);
    }


    public List<String> getAllVehicleTypes() {
        List<Product> products = productRepository.findAll();
        Set<String> types = new HashSet<String>();
        String tmp;
        for (Product p : products) {
            tmp = p.getCarType();
            if (tmp != null) {
                String[] carTypes = tmp.split(";");
                for (int i = 0; i < carTypes.length; i++) {
                    types.add(carTypes[i]);
                }
            }
        }
        types.remove("");
        return new ArrayList<String>(types);
    }


    public List<Product> getAllProductsByVehicleType(final String vehicle) {
        /*List<Product> products = productRepository.findAll();
        for (Product p : products) {
        if (!p.getCarType().contains(vehicle)) {
        products.remove(p);
        }
        }
        return products;*/
        return productRepository.findProductsContainingEntryCarType(vehicle);
    }

    public List<Product> searchProducts(String fulltextQuery) {
        return searcher.searchProducts(fulltextQuery);
    }

    public void setMySession(MySession mySession) {
        this.mySession = mySession;
    }

    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public List<Product> findTopFavouriteProducts(int amount) {
        List<Product> products = productRepository.findAll();
        Collections.sort(products, new ProductFavouriteComparator());
        if (amount > products.size()) {
            amount = products.size();
        }
        List<Product> topProducts = new ArrayList<Product>(amount);
        for (int i = 0; i < amount; i++) {
            topProducts.add(products.get(i));
        }
        return topProducts;
    }

    /**
     * Personal comparator, compare products by merchantability.
     */
    private static final class ProductFavouriteComparator implements Comparator<Product> {

        public int compare(Product o1, Product o2) {
            return (o1.getSold() - o2.getSold());
        }
    }

    public void setVelocityService(VelocityService velocityService) {
        this.velocityService = velocityService;
    }
}
